﻿// $Id: ServerManager.cs 11 2008-08-20 15:24:25Z jkingry $

namespace Mithril.Server
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.IO;
    using System.Net.NetworkInformation;
    using System.Net.Sockets;
    using System.Threading;
    using Mithril.Server.Properties;
    
    class ServerManager : IDisposable
    {
        private class ProgressTraceListener : TraceListener
        {
            public ProgressTraceListener(ServerManager parent)
            {
                this.parent = parent;
            }

            public override void Write(string message)
            {
                if(this.parent.operation != null)
                {
                    this.parent.operation.Post(x =>
                        {
                            if (this.parent.ProgressChanged != null)
                            {
                                this.parent.ProgressChanged(this.parent, new ProgressChangedEventArgs(0, x));
                            }
                        }, message);
                }
            }

            public override void WriteLine(string message)
            {
                this.Write(message + Environment.NewLine);
            }

            ServerManager parent;
        }

        public event AsyncCompletedEventHandler StartCompleted;
        public event ProgressChangedEventHandler ProgressChanged;

        public bool Running
        {
            get
            {
                return this.running;
            }
        }

        public ServerManager()
        {
            using (Stream s = typeof(ServerManager).Assembly.GetManifestResourceStream("Mithril.Server.Policy.xml"))
            {
                int offset = 0;
                int count = (int) s.Length;
                this.policy = new byte[count];

                while (count > 0)
                {
                    int read = s.Read(this.policy, offset, count);
                    offset += read;
                    count -= read;
                }
            }

            networkChangedHandler = new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);
            NetworkChange.NetworkAddressChanged += networkChangedHandler;
            networkAvailableHandler = new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
            NetworkChange.NetworkAvailabilityChanged += networkAvailableHandler;

            this.traceListener = new ProgressTraceListener(this);
            Trace.Listeners.Add(this.traceListener);
        }        

        public void StartAsync()
        {
            if (this.operation != null)
                throw new InvalidOperationException("Server is already running");

            this.operation = AsyncOperationManager.CreateOperation(null);

            ThreadPool.QueueUserWorkItem(x => { this.StartInternal(); });
        }


        public void StopAsync()
        {
            if (this.operation == null)
                throw new InvalidOperationException("Server is not running");

            ThreadPool.QueueUserWorkItem(x => { this.StopInternal(null); });
        }

        public void Dispose()
        {
            NetworkChange.NetworkAddressChanged -= this.networkChangedHandler;
            NetworkChange.NetworkAvailabilityChanged -= this.networkAvailableHandler;
            if (this.running)
            {
                this.StopInternal(null);
            }
            Trace.Listeners.Remove(this.traceListener);
        }

        private void StartInternal()
        {
            lock (internal_lock)
            {
                if (this.running) return;
                if (this.starting) return;
                
                this.starting = true;

                try
                {
                    this.policy_server = new PolicyServer(this.policy);
                    this.policy_server.Start();

                    this.signal_server = new SignalServer();
                    this.signal_server.Start();

                    //Register server
                    ServiceResult result = null;
                    if (String.IsNullOrEmpty(settings.ServerKey))
                    {
                        result = proxy.Create(settings.AccountKey, settings.Name);
                    }
                    else
                    {
                        result = proxy.Update(settings.AccountKey, settings.ServerKey, true);
                    }
                    settings.ServerKey = result.ServerKey;
                    settings.Save();

                    this.publicIpAddress = result.IpAddress;

                    //Test server
                    Exception x = TestConnectivity();
                    if (x != null) throw x;

                    this.running = true;
                }
                catch (Exception x)
                {
                    settings.ServerKey = String.Empty;
                    settings.Save();

                    if (Debugger.IsAttached)
                        Debugger.Break();

                    this.StopInternal(x);
                }
                this.starting = false;
            }
        }

        private void StopInternal(Exception exception)
        {
            lock(this.internal_lock)
            {
                if (this.closing) return;
                this.closing = true;
                
                if (this.policy_server != null)
                {
                    this.policy_server.Stop();
                    this.policy_server = null;
                }

                if (this.signal_server != null)
                {
                    this.signal_server.Stop();
                    this.signal_server = null;
                }

                try
                {
                    if (!String.IsNullOrEmpty(settings.ServerKey))
                    {
                        proxy.Update(settings.AccountKey, settings.ServerKey, false);
                    }
                }
                catch (Exception)
                {

                }
                this.closing = false;
                this.running = false;

                AsyncCompletedEventArgs e = new AsyncCompletedEventArgs(exception, exception != null, null);

                this.operation.PostOperationCompleted(x =>
                {
                    if (this.StartCompleted != null)
                        this.StartCompleted(this, e);
                }, e);
                this.operation = null;
            }            
        }

        private Exception TestConnectivity()
        {
            try
            {
                TcpClient client = new TcpClient();
                client.Connect(this.publicIpAddress, PolicyServer.PolicyServerPort);
                client.Close();
            } catch(Exception x)
            {
                return new Exception("Could not connect to policy server", x);
            }

            try
            {
                TcpClient client = new TcpClient();
                client.Connect(this.publicIpAddress, SignalServer.SignalServerPort);
                client.Close();
            }
            catch (Exception x)
            {
                return new Exception("Could not connect to signal server", x);
            }

            return null;
        }

        private void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            Exception x = TestConnectivity();
            if (x != null)
            {
                this.StopInternal(x);
            }
        }

        private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
        {
            Exception x = TestConnectivity();
            if (x != null)
            {
                this.StopInternal(x);
            }
            else
            {
                this.proxy.Update(settings.AccountKey, settings.ServerKey, true);
            }
        }

        private ProgressTraceListener traceListener;

        private AsyncOperation operation;

        private string publicIpAddress;

        private bool running, starting, closing;
        private PolicyServer policy_server;
        private SignalServer signal_server;
        private ServiceProxy proxy = new ServiceProxy();
        private Settings settings = Settings.Default;

        private object internal_lock = new object();

        private NetworkAddressChangedEventHandler networkChangedHandler;
        private NetworkAvailabilityChangedEventHandler networkAvailableHandler;

        private byte[] policy;
    }
}
